#pragma once

#include <string>
#include <list>
#include <thread>
#include <memory>
#include "module.hh"
#include "../corelib/spsc_queue.hpp"
#include "../corelib/util/allocator.hh"

namespace kratos { namespace engine {

using ModuleMap = std::unordered_map<std::string, Module>;
using ModulePtrList = std::list<Module*>;

class Engine;

// Module manager
class ModuleManager {
    ModuleMap moduleMap_; // Module table
    using StringList = std::list<std::string>;
    StringList whiteList_; // White list
    struct HashFunc {
        std::size_t operator()(const ModuleType& type) const {
            return static_cast<std::size_t>(type);
        }
    };
    struct EqualFunc {
        bool operator()(const ModuleType& a, const ModuleType& b) const {
            return (a == b);
        }
    };
    using TypicalModuleMap = std::unordered_map<ModuleType, ModulePtrList, HashFunc, EqualFunc>;
    TypicalModuleMap typicalModuleMap_; // { ModuleType, module pointer list }
    Engine* engine_; // Engine
    // Upgrading request
    struct UpgradeRequest {
        UpgradeRequest(const std::string& root, const std::string& moduleName) {
            baseDir = root;
            name = moduleName;
        }
        std::string baseDir;
        std::string name;
    };
    using ServiceModulePathList = std::list<std::string>;
    ServiceModulePathList serviceModulePathList_; // [ Module DIR path ]
    corelib::SPSCQueue<UpgradeRequest*> upgradeQueue_; // The queue for upgrading event
    std::unique_ptr<std::thread> upgradeScanner_; // The standalone thread to check upgrading
    bool run_; // The running flag for scanner
    bool pauseScanner_; // Pause scanner
    std::time_t scannerIntval_; // The interval of scanner thread to check upgrading
    constexpr static std::time_t SCANNER_INTERVAL = 3; // The default sanning interval for upgrading
    using StringVector = std::vector<std::string>;
    using ServiceRootPathMap = std::unordered_map<std::string, std::string>;
    ServiceRootPathMap serviceRootPathMap_; // { Service name, Service Root Path }

public:
    // ctor
    ModuleManager(Engine* engine);
    // dtor
    ~ModuleManager();
    // Load module
    // @param serviceDir Module directory
    // @retval true
    // @retval false fail
    bool loadModule(const std::string& serviceDir);
    // Unload module
    // @param name Module name
    // @retval true
    // @retval false fail
    bool unloadModule(const std::string& name);
    // unload all modules
    void unloadAll();
    // Upgrades module
    // @param baseDir The father diretory of module
    // @param name Module name
    // @retval true
    // @retval false fail
    bool upgradeModule(const std::string& baseDir, const std::string& name);
    // returns module reference
    Module* getModule(const std::string& name);
    // returns ModuleMap
    ModuleMap& getModules();
    // Returns modules by type
    const ModulePtrList& getTypicalModules(ModuleType type);
    // tick
    void onTick(std::time_t current);
    // add to white list
    void addToWhite(const std::string& name);
    // Setup The interval of scanner thread to check upgrading
    void setUpgradingInterval(std::time_t intval);
    // Returns the service path root by service name
    const std::string& getServiceRootPath(const std::string& serviceName);
    // Returns service name
    static std::string getServiceName(const std::string& path);
    // Returns the service path root
    static std::string getServicePathRoot(const std::string& path);   

private:
    // Is in white list?
    // @param fileName the module file needs to load
    // @retval true
    // @retval false
    bool isInWhiteList(const std::string& fileName);
    // Try to load services for debug mode
    // @param path the module file needs to load
    // @retval true
    // @retval false
    bool tryLoadServiceInDebug(const std::string& path);
    // Roll back the old module
    // @param srcFilePath The original module file
    // @param name module name
    // @retval true
    // @retval false
    bool rollback(const std::string& srcFilePath, const std::string& filePathBackup, const std::string& name);
    // Check service module
    bool checkServiceModule(const std::string& filePath, const std::string& name);
    // Rename the patch files
    bool renamePatchFiles(const std::string& srcFilePath, const std::string& patchFilePath, const std::string& filePathBackup);
    // Start scanning thread
    void startSanner();
    // Deal with upgrading request
    void dealWithUpgradeRequest();
    // Tick all module
    void tickModules(std::time_t current);
    // Scan the service module's patch
    void scanPatch();
    // Get latest backup name
    std::string getLatestBackupName(const std::string& dir, const std::string& scrFileName);
};

}}
